from flask import render_template
from flask import Blueprint
import json
from .. import app, serial, mqtt
from ..models import *

import binascii
import threading
from datetime import datetime
import time

from .help import Help
help = Help()



TYPE = "01"

SITE_ID = "53010000"
# 节点超时时间-秒
NODE_ALIVE_TIMEOUT = 30
# 节点保持连接时间-秒
NODE_ALIVE_KEEP = 60
# 节点心跳包
NODE_ALIVE = "B0"
# 节点设备打开阀门
NODE_OPEN_TAP = "B1"
# 节点设备关闭阀门
NODE_CLOSE_TAP = "B2"
# 节点请求连接终端
NODE_REQUEST_CONNECT = "B3"
# 节点设备上线
NODE_ALIVE_ON = "B4"
# 节点设备离线
NODE_ALIVE_OFF = "B5"
# 节点设备不在线
NODE_NOT_ALIVE = "B6"
# 阀门打开状态
NODE_TAP_IS_OPEN = "B7"
# 阀门关闭状态
NODE_TAP_IS_CLOSE = "B8"
# 节点设备未注册
NODE_NOT_REGISTER = "B9"

# 用户获取所有节点
USER_GET_NODES = "C0"


idx = Blueprint('idx', __name__, template_folder='templates')


@idx.route('/')
def index():
    node = db.session.query(Node).all()
    print(node)
    return render_template('base.html')

@idx.route('/serial')
def serial_index():
    return render_template('serial.html')

'''
SocketIO Functions
'''

# @socketio.on('publish')
# def handle_publish(json_str):
#     print("推送消息")
#     data = json.loads(json_str)
#     mqtt.publish(data['topic'], data['message'])
#
# @socketio.on('subscribe')
# def handle_subscribe(json_str):
#     data = json.loads(json_str)
#     print(data)
#     mqtt.subscribe(data['topic'])
#
# @socketio.on('send')
# def handle_send(json_str):
#     data = json.loads(json_str)
#     serial.on_send(data['message'])
#     print("send to serial: %s"%data['message'])

"""
Mqtt 功能
包含：
    1、mqtt订阅用户的消息
    2、mqtt接收用户的消息并通过串口转发到响应的节点设备中
    3、mqtt
"""

# mqtt主题订阅
topics = ["17740171896"]
for t in topics:
    mqtt.subscribe(topic=t, qos=0)

publish_site = "53010000"

@mqtt.on_message()
def handle_mqtt_message(client, userdata, message):
    topic=message.topic
    payload=str(message.payload.decode())
    li = payload.split('/')
    if li:
        pkg_addr = li[0]
        pkg_type = li[1]
        pkg_cmd = li[2]
        print("接收到命令：",pkg_cmd)
        if pkg_cmd == USER_GET_NODES:
            with app.app_context():
                nodes = db.session.query(Node).all()
                nodes_list = []
                for node in nodes:
                    nodes_dict = {
                        "cid": node.cid,
                        "name": node.name,
                        "version": node.version,
                        "alive": node.alive,
                        "status": node.status,
                        "tap_status": node.tap_status
                    }
                    nodes_list.append(nodes_dict)
                site = db.session.query(Site).first()
                equ = {
                    "nodes": nodes_list,
                    "site": {
                        "cid": site.cid,
                        "name": site.name,
                        "version": site.version,
                        "alive": site.alive,
                        "status": site.status
                    }
                }
                mqtt.publish(topic=publish_site, payload='%s/%s/%s/%s'%(site.cid, TYPE, USER_GET_NODES, json.dumps(equ)))
        if pkg_cmd == NODE_OPEN_TAP or pkg_cmd == NODE_CLOSE_TAP:
            print("控制阀门打开")
            with app.app_context():
                node = db.session.query(Node).filter(Node.cid == pkg_addr).first()
                if node:
                    if node.alive:
                        serial.on_send(
                            help.lora_get_cmd(
                                # lora_addr=str(node.lora_addr).replace(' ',''),
                                target=node.cid,
                                _type=TYPE,
                                cmd=pkg_cmd
                            )
                        )
                    else:
                        # 阀门不在线
                        mqtt.publish(topic=publish_site, payload="%s/%s/%s"%(node.cid, TYPE, NODE_NOT_ALIVE))

@mqtt.on_log()
def handle_logging(client, userdata, level, buf):
    print(level, buf)

"""
节点心跳包处理

思路：
        客户端连接上服务端后，在服务端会维护一个在线客户端列表。
    客户端每隔一段时间，向服务端发送一个心跳包，服务端受收到包以后，会更新客户端最近一次在线时间。
    一旦服务端超过规定时间没有接收到客户端发来的包，则视为掉线。
    
    1、打开一个线程，扫描在线的节点设备。
    2、根据节点设备的心跳包更新的最近在线时间和当前时间做差值
    3、超过规定时间，修改设备在线状态

"""
def heartHandler():
    while True:
        time.sleep(1)
        with app.app_context():
            nodes = db.session.query(Node).filter(Node.alive==True).all()
            for node in nodes:
                invertal_time = datetime.now() - node.alive_time
                if invertal_time.seconds > 3:
                    print("%s 设备超时下线 "%node.name)
                    node.alive = False
                    site = db.session.query(Site).filter(Site.id==node.site_id).first()
                    if site:
                        mqtt.publish(topic=site.cid,
                                     payload='{cid}/{type}/{cmd}'.format(cid=node.cid, type=TYPE, cmd=NODE_ALIVE_OFF)
                                    )
                else:
                    node.alive = True
            db.session.commit()
heart_thread = threading.Thread(target=heartHandler)
heart_thread.setDaemon(True)
heart_thread.start()


"""
串口处理功能
    1、串口接收
    2、数据包分析，包含数据头、数据长度、设备地址、类型、命令、数据、校验
    3、查询设备地址是否注册到当前终端中
    4、根据命令处理相应的功能
"""

@serial.on_message()
def serial_message(msg):
    data = str(binascii.b2a_hex(msg).decode('utf-8')).upper()
    res = help.lora_cmd_deal(data)
    if res:
        pkg_addr = res.get('addr')
        pkg_cmd  = res.get('cmd')
        pkg_type = res.get('type')
        pkg_data = res.get('data')
        if pkg_cmd == NODE_REQUEST_CONNECT:
            print("接收到节点请求连接。。。。。")
            cid = pkg_data
            with app.app_context():
                node = db.session.query(Node).filter(Node.cid == cid.lower()).first()
                if not node:
                    print("%s未注册" % node)
                else:
                    print("%s已注册"%node.name)
                    # 添加上线时间
                    node.alive_time = datetime.now()
                    node.alive = True
                    db.session.commit()
                    # mqtt发送上线消息给用户
                    site = db.session.query(Site).filter(Site.id == node.site_id).first()
                    if site:
                        mqtt.publish(topic=site.cid,
                                     payload='{cid}/{type}/{cmd}'.format(cid=node.cid, type=TYPE, cmd=NODE_ALIVE_ON)
                                     )
                    # 发送请求响应
                    send_cmd = help.lora_get_cmd(
                        # lora_addr=str(node.lora_addr).replace(' ',''),
                        target = node.cid,
                        _type = TYPE,
                        cmd = NODE_REQUEST_CONNECT
                    )
                    if send_cmd:
                        serial.on_send(send_cmd)
        if pkg_cmd == NODE_ALIVE:
            print("接受到节点: %s 心跳包"% pkg_addr)
            with app.app_context():
                node = db.session.query(Node).filter(Node.cid == pkg_addr.lower()).first()
                if not node:
                    mqtt.publish(topic=SITE_ID,
                                 payload="%s/%s/%s" % (pkg_addr, TYPE, NODE_NOT_REGISTER))
                else:
                    # node.alive_time = datetime.strptime(datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"%Y-%m-%d %H:%M:%S")
                    node.alive_time = datetime.now()
                    db.session.commit()
        if pkg_cmd == NODE_TAP_IS_OPEN or pkg_cmd == NODE_TAP_IS_CLOSE:
            with app.app_context():
                node = db.session.query(Node).filter(Node.cid == pkg_addr.lower()).first()
                if not node:
                    mqtt.publish(topic=SITE_ID,
                                 payload="%s/%s/%s"%(pkg_addr, TYPE, NODE_NOT_REGISTER))
                else:
                    mqtt.publish(topic=SITE_ID,
                                 payload="%s/%s/%s" % (pkg_addr, TYPE, pkg_cmd))
                if pkg_cmd == NODE_TAP_IS_OPEN:
                    node.tap_status = True
                if pkg_cmd == NODE_TAP_IS_CLOSE:
                    node.tap_status = False
                db.session.commit()


@serial.on_log()
def serial_log(level, info):
    print(info)


